Skip to content

fix(tools-pack): focus existing AppImage instead of silent EADDRINUSE on second launch#2262

Closed
n8k99 wants to merge 2 commits into
nexu-io:mainfrom
n8k99:claudian/launcher-already-running-focus
Closed

fix(tools-pack): focus existing AppImage instead of silent EADDRINUSE on second launch#2262
n8k99 wants to merge 2 commits into
nexu-io:mainfrom
n8k99:claudian/launcher-already-running-focus

Conversation

@n8k99
Copy link
Copy Markdown

@n8k99 n8k99 commented May 19, 2026

Summary

startPackedLinuxApp had no already-running detection. The .desktop entry runs open-design which always calls linux start, which tries to bind the same desktop IPC socket — second click silently EADDRINUSEs and the launcher appears to "do nothing."

This adds:

  • detectRunningLinuxApp() — mirrors stopPackedLinuxApp's marker + stamp + IPC liveness validation, but returns the live instance instead of killing it
  • focusLinuxWindow() — tries swaymsg / i3-msg / hyprctl based on WM env vars, with wmctrl as the universal fallback
  • Fast path in startPackedLinuxApp: if a live instance is detected, focus its window and return source: "already-running" (new variant)

Test plan

  • pnpm --filter @open-design/tools-pack typecheck — clean
  • pnpm --filter @open-design/tools-pack test — 66 tests, 5 new for focusAttemptsForLinuxWindow
  • pnpm guard — clean
  • Live: cold-start spawns AppImage (source: "installed"), warm-start hits new path (source: "already-running", focusMethod: "swaymsg", status returned via IPC)
  • No --force option surfaced on the new pr merge subcommand in forgejo-cli (matches the never-force-merge hard rail)

Notes

LinuxStartResult widens with optional focusMethod (string|null), populated only on "already-running". Backward compatible — existing consumers ignore the new field.

Nathan Eckenrode and others added 2 commits May 17, 2026 17:49
Two new design systems based on Nathan's existing DPN.md / DPS.md style
guides (canonical at ~/Documents/DPN.md and ~/Documents/DPS.md). They
codify a single design language with a dark face (Noir) and a light face
(Solar), originally authored for AI image generation and now extended
into UI tokens via the typographic-roles addition from 2026-04-27.

DragonPunk Noir (DPN) — dark-by-default:
- Canvas: Void Black #0A0A0F (warmest possible black, never #000)
- Ink: Dragon Gold #FFB800 — load-bearing read color at ~12:1 on void
- Accent + Focus glow: Neon Cyan #00F5FF — single token (~110:1 on void
  lets it serve both readable-accent and focus-glow roles)
- Neon family: Magenta #FF2D95, Purple #9D00FF, Toxic Green #39FF14
- Warm spike: Ember #FF6B35 (warn), Crimson #8B0000 (deep danger),
  Bronze #CD7F32 (stale/disabled)
- Plan 9 / Acme / NeXTSTEP lineage: square corners, no shadows, no blur,
  state-change motion only

DragonPunk Solar (DPS) — light-face twin:
- Canvas: Solar White #FFF8E1 (cream warmed toward gold, never sterile)
- Ink: Bole #3D1208 — Renaissance gilders' red-clay underground at ~13:1
- Readable accent: Rubric #B82F08 — manuscript-red, ~5.5:1
- Focus glow only: Plasma Cyan #00D4FF (cyan-on-cream collapses to
  ~1.7:1, so DPS splits the role into Rubric + Cyan rather than
  collapsing into one token like DPN does)
- Dark-mode override flips DPS to DPN's full token set (same brand,
  light face flips to dark face — not duplicated values per Lens B)

Both files satisfy the 9-section schema (Visual Theme / Color /
Typography / Spacing / Layout / Components / Motion / Voice /
Anti-patterns), include real CSS in :root blocks, use the
[data-theme="dark"] override pattern, and target prefers-reduced-motion
to specific selectors rather than global *. Substantive anti-patterns
named (no rounded corners, no drop shadows, no ambient idle animation,
no centered prose, no cyan-as-text on cream, etc.). Prior art names
real specifics: Plan 9, Acme, NeXTSTEP, Blade Runner 2049, Renaissance
manuscript rubrication, the Mixxx LateNight skins, dpn-void.css /
dps-solar.css.

Category: Bold & Expressive for both.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
… on second launch

startPackedLinuxApp had no already-running detection. Every CMD+SPACE
"Open Design" launch after the first one tried to bind the same desktop
IPC socket at /tmp/open-design/ipc/<ns>/desktop.sock, hit EADDRINUSE,
and exited silently. Observed as the launcher "doing nothing" on the
second click.

This adds detectRunningLinuxApp() (mirrors stopPackedLinuxApp's marker
+ stamp + IPC liveness validation) and focusLinuxWindow() (swaymsg /
i3-msg / hyprctl by env, wmctrl as universal fallback), so the fast
path focuses an existing window instead of spawning a duplicate.

- LinuxStartSource widened to include "already-running"
- LinuxStartResult gains optional focusMethod
- 5 new tests for focusAttemptsForLinuxWindow (66 total, all pass)
- pnpm guard / typecheck / --filter @open-design/tools-pack test all green
@lefarcen lefarcen requested a review from PerishCode May 19, 2026 12:19
@lefarcen lefarcen added size/XL PR changes 700-1500 lines risk/medium Medium risk: regular code changes type/bugfix Bug fix labels May 19, 2026
@PerishCode
Copy link
Copy Markdown
Contributor

@n8k99 I'm holding off on generating review comments for #2262 because this pull request has merge conflicts right now.

Please resolve the conflicts with main and push the updated branch. Once that's done, request or wait for the review to run again and I'll take another look.

🔁 Powered by Looper · runner=reviewer · agent=claude-code · An autonomous AI dev team for your GitHub repos.

Copy link
Copy Markdown
Contributor

@lefarcen lefarcen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @n8k99! The Summary gives a clear picture of the AppImage second-launch failure and the intended already-running path. One quick body-template ask before pool review: could you add a ## Surface area checklist and a ## Validation section? The current test-plan bullets are helpful; putting them under Validation and ticking the affected surface area will make the review/release scope easier to scan.

@github-actions
Copy link
Copy Markdown
Contributor

@n8k99 friendly reminder: this PR has been waiting on an author response for more than 3 days after reviewer or maintainer feedback.

When you have a chance, please reply here or push an update. To keep the queue manageable, PRs with no author activity for more than 5 days after feedback may be closed automatically, but they can be reopened when work resumes.

Copy link
Copy Markdown
Contributor

@PerishCode PerishCode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@n8k99 Two surfaces ride together in this PR: the tools-pack already-running fast-path described by the title (head commit ffec29d5), and an unrelated feat(design-systems) commit (d9605872) adding DragonPunk Noir + Solar. The bug-fix logic itself is careful and mirrors stopPackedLinuxApp's validation shape closely, with sensible WM detection via env vars. Two findings hold up the merge; two more inline are code-level non-blocking suggestions.

Blocking

  • Commit d9605872 includes a Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> trailer. AGENTS.md "Git commit policy" is explicit: "Git commits must not include Co-authored-by trailers or any other co-author metadata." The head commit ffec29d5 is clean. Please rebase d9605872 to drop the trailer and force-push the branch.

Non-blocking (scope)

  • The PR title and Summary cover only tools/pack/src/linux.ts, but the patch also lands two new design-systems/dragonpunk-*/DESIGN.md files (780 added lines, a full second commit) that are invisible to anyone reading the title/body. Per docs/code-review-guidelines.md, design-system additions are a separate review lane with their own checklist (9-section schema, anti-patterns, prior art, contrast). Splitting d9605872 into its own PR lets the launcher fix merge on its own merits and lets the design-system content get proper lane review instead of riding along under a fix(tools-pack): title.

Inline comments cover the two code-level notes (wmctrl PID disambiguation, redundant STATUS IPC call).

🔁 Powered by Looper · runner=reviewer · agent=claude-code · An autonomous AI dev team for your GitHub repos.

Comment thread tools/pack/src/linux.ts
Comment on lines +843 to +851
try {
await requestJsonIpc(
marker.stamp.ipc,
{ type: SIDECAR_MESSAGES.STATUS },
{ timeoutMs: 1000 },
);
} catch {
return null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Liveness probe duplicates the STATUS call that fetchDesktopStatus makes immediately afterward.

This requestJsonIpc(..., { type: STATUS }, { timeoutMs: 1000 }) runs and discards its reply, then the already-running branch in startPackedLinuxApp hits the same socket again with await fetchDesktopStatus(config) to populate status in the returned LinuxStartResult. Two STATUS round-trips per launcher click against the same desktop IPC socket, where one would do.

Not a correctness issue, just wasted work on every already-running click — and fetchDesktopStatus swallows its own errors (returns null), so the second round-trip can also mask a flake the first one already proved didn't exist.

Suggested change: have detectRunningLinuxApp return the parsed DesktopStatusSnapshot | null from its liveness probe alongside the DetectedRunningInstance, then reuse it as status in the already-running result and skip the fetchDesktopStatus(config) call when the fast path fires.

Comment thread tools/pack/src/linux.ts
Comment on lines +892 to +896
attempts.push({
args: ["-x", "-a", "Open Design"],
command: "wmctrl",
method: "wmctrl",
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wmctrl fallback can focus the wrong AppImage when more than one namespace is live.

The three WM-specific commands above (swaymsg, i3-msg, hyprctl) all carry [pid="${pid}"] / pid:${pid} selectors, so they target the exact AppImage the user just clicked. The wmctrl -x -a "Open Design" fallback matches by window class only — when a user has two namespaces (e.g. default + pr-2262) running, both AppImages publish the same class, and wmctrl -a activates whichever appears first in its window list, which is often the wrong one.

This is strictly better than the pre-PR silent EADDRINUSE, so it isn't a regression. The concern is that on legacy DEs (GNOME / KDE / XFCE without sway/i3/hyprland sockets) this is the only attempt that ever runs, which is exactly where the multi-namespace mismatch will bite.

Suggested change: enumerate windows via wmctrl -lpx, filter rows whose PID column matches pid, then wmctrl -ia <hex-id>. Fall back to the current class-only wmctrl -x -a only when no row matches (e.g. wmctrl too old to report pid, or window not yet mapped).

@github-actions
Copy link
Copy Markdown
Contributor

Closing this PR for now because it has been waiting on an author response for more than 5 days after reviewer or maintainer feedback.

This is only a queue-management step, not a rejection of the work. If you would like to continue, please leave a comment or push an update and reopen the PR when ready.

@github-actions github-actions Bot closed this May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk/medium Medium risk: regular code changes size/XL PR changes 700-1500 lines type/bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants